Skip to content

仲灏小栈

仲灏约 1 分钟

在 NestJS 中,Service 本身是单例模式(默认),而 Request 对象是请求级别的上下文,因此直接在 Service 中获取 req 需要结合「作用域」「显式传递」或「异步本地存储(ALS)」实现。以下是 3 种常用方案,按「推荐优先级」排序:

方案 1:控制器显式传递 req(最推荐,解耦)

这是最符合 NestJS 设计理念的方式:控制器层获取 req,调用 Service 时作为参数传递。Service 保持单例,不耦合请求上下文,通用性强。

实现步骤:

  1. 控制器中通过 @Req() 装饰器获取请求对象;
  2. 调用 Service 方法时将 req 作为参数传入。

typescript

运行

typescript
// user.controller.ts
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express'; // 若用 Fastify 则导入 fastify 的 Request
import { UserService } from './user.service';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  async getCurrentUser(@Req() req: Request) {
    // 控制器获取 req,传递给 Service
    return this.userService.getCurrentUser(req);
  }
}

// user.service.ts
import { Injectable } from '@nestjs/common';
import { Request } from 'express';

@Injectable() // 保持默认单例,无性能开销
export class UserService {
  getCurrentUser(req: Request) {
    // 直接使用传递过来的 req
    const userId = req.user?.id; // 示例:获取用户 ID
    const ip = req.ip; // 示例:获取客户端 IP
    return { userId, ip };
  }
}

方案 2:注入 REQUEST 作用域(简单但有性能开销)

通过将 Service 的作用域设置为 Scope.REQUEST,并注入 REQUEST 令牌,直接在 Service 中获取 req。

⚠️ 注意:REQUEST 作用域的 Service 每次请求都会创建新实例,频繁使用会增加性能开销,仅适合简单场景。

实现步骤:

  1. 导入 REQUESTInjectScope
  2. 装饰器中设置 scope: Scope.REQUEST
  3. 构造函数中注入 REQUEST 对象。

typescript

运行

typescript
// user.service.ts
import { Injectable, Inject, Scope } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';

@Injectable({ scope: Scope.REQUEST }) // 设置为请求作用域
export class UserService {
  // 注入 REQUEST 对象
  constructor(@Inject(REQUEST) private readonly request: Request) {}

  getCurrentUser() {
    // 直接使用 this.request
    const userId = this.request.user?.id;
    const ip = this.request.ip;
    return { userId, ip };
  }
}

// 控制器无需传参,直接调用
// user.controller.ts
@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  async getCurrentUser() {
    return this.userService.getCurrentUser();
  }
}

方案 3:AsyncLocalStorage(复杂场景无耦合)

对于多层级 Service 调用(如 Service A → Service B → Service C 都需要 req),显式传递参数会很繁琐,此时可使用 Node.js 的 AsyncLocalStorage(ALS)实现「请求上下文共享」。

实现步骤:

1. 创建请求上下文工具类

typescript

运行

typescript
// src/common/request-context.ts
import { AsyncLocalStorage } from 'async_hooks';
import { Request } from 'express';

// 创建 ALS 实例,存储 Request 对象
const asyncLocalStorage = new AsyncLocalStorage<Request>();

// 启动请求上下文(在中间件中调用)
export const runInRequestContext = (req: Request, callback: () => void) => {
  asyncLocalStorage.run(req, callback);
};

// 获取当前请求的 req 对象
export const getRequestContext = (): Request => {
  const req = asyncLocalStorage.getStore();
  if (!req) {
    throw new Error('请求上下文未初始化,请确保中间件已生效');
  }
  return req;
};
2. 创建全局中间件,初始化上下文

typescript

运行

typescript
// src/middleware/request-context.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { runInRequestContext } from '../common/request-context';

@Injectable()
export class RequestContextMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // 将 req 存入 ALS,并执行后续逻辑
    runInRequestContext(req, next);
  }
}
3. 注册全局中间件

typescript

运行

typescript
// src/app.module.ts
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
import { RequestContextMiddleware } from './middleware/request-context.middleware';

@Module({
  // ... 其他配置
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    // 全局注册中间件
    consumer.apply(RequestContextMiddleware).forRoutes('*');
  }
}
4. Service 中获取 req(无需传参、无作用域限制)

typescript

运行

typescript
// user.service.ts
import { Injectable } from '@nestjs/common';
import { getRequestContext } from '../common/request-context';

@Injectable() // 保持单例,无性能开销
export class UserService {
  getCurrentUser() {
    // 从 ALS 中获取 req
    const req = getRequestContext();
    const userId = req.user?.id;
    const ip = req.ip;
    return { userId, ip };
  }
}

// 其他层级的 Service 也可直接调用 getRequestContext()
// order.service.ts
@Injectable()
export class OrderService {
  constructor(private readonly userService: UserService) {}

  getOrderUser() {
    const req = getRequestContext(); // 直接获取 req
    return { orderId: 123, userId: req.user?.id };
  }
}

总结

方案优点缺点适用场景
控制器显式传递解耦、性能好、易维护多层调用需传参大多数常规场景
REQUEST 作用域实现简单、无需传参性能开销、Service 非单例简单场景(仅单个 Service 使用)
AsyncLocalStorage无耦合、多层调用便捷配置稍复杂复杂层级调用场景

最佳实践:优先使用「控制器显式传递」;简单场景可使用「REQUEST 作用域」;复杂多层调用用「AsyncLocalStorage」。尽量避免 Service 直接依赖 req,仅在必要时获取(如 IP、用户信息等)。

上次更新: